package com.bestv.search.engine.servlet;

import com.bestv.search.common.model.*;
import com.bestv.search.common.util.DateUtil;
import com.bestv.search.common.util.QueryStringProcess;
import org.compass.core.*;
import org.compass.core.CompassQueryBuilder.CompassBooleanQueryBuilder;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.Set;

public final class SearchServlet extends HttpServlet {

    private static final long serialVersionUID = 1538950684027601589L;
    static Compass compass;

    public void destroy() {
        super.destroy();
    }

    /**
     * Initialization of the servlet. <br>
     *
     * @throws ServletException if an error occurs
     */
    public void init() throws ServletException {
        ServletContext application = getServletContext();
        WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(application);
        compass = (Compass) wac.getBean("compass");
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        search(request, response);
    }

    private void search(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");
        String requestJson = request.getParameter("q");
        String keywords;
        CompassSearchSession session = compass.openSearchSession();
        CompassQueryBuilder queryBuilder = session.queryBuilder();
        CompassBooleanQueryBuilder boolQuery = queryBuilder.bool();
        CompassBooleanQueryBuilder keyQuery = queryBuilder.bool();
        try {
            JSONObject jo = new JSONObject(requestJson);
            try {
                keywords = jo.getString("keywords");
                if (keywords == null || "".equals(keywords)) {
                    response.getWriter().write("error: keywords cannot be null");
                    return;
                }
                keywords = keywords.replaceAll("[ ]+", " OR ");
                keywords = QueryStringProcess.replaceAllIllegleChar(keywords);
                keywords = "(" + keywords + ")";
            } catch (JSONException e) {
                e.printStackTrace();
                response.getWriter().write("error: keywords is wrong");
                return;
            }
            try {
                String fields = jo.getString("fields");
                if (fields != null && !"".equals(fields)) {
                    keyQuery.addShould(queryBuilder.queryString("manualBind:" + keywords).toQuery());
                    String[] fds = fields.split("[ ]+");
                    for (String fd : fds)
                        keyQuery.addShould(queryBuilder.queryString(fd + ":" + keywords).toQuery());
                } else
                    throw new JSONException("fields is null");
            } catch (JSONException e) {
                keyQuery.addShould(queryBuilder.queryString("manualBind:" + keywords).toQuery());
                keyQuery.addShould(queryBuilder.queryString("name:" + keywords).toQuery());
                keyQuery.addShould(queryBuilder.queryString("tag:" + keywords).toQuery());
                keyQuery.addShould(queryBuilder.queryString("keyword:" + keywords).toQuery());
            }
            boolQuery.addMust(keyQuery.toQuery());
            try {
                JSONArray filterArray = jo.getJSONArray("filters");
                for (int i = 0; i < filterArray.length(); ++i) {
                    JSONObject filterObj = filterArray.getJSONObject(i);
                    String filterType = filterObj.getString("filterType").trim();
                    String filterField = filterObj.getString("filterField").trim();
                    String filterValue = filterObj.getString("filterValue").trim();
                    if ("or".equalsIgnoreCase(filterType)) {
                        if (!"".equals(filterValue)) {
                            filterValue = filterValue.replaceAll("[ ]+", " OR ");
                            boolQuery.addMust(queryBuilder.queryString(filterField + ":(" + filterValue + ")")
                                    .toQuery());
                        }
                    } else if ("range".equalsIgnoreCase(filterType)) {
                        if (!"".equals(filterValue)) {
                            String[] rangeValues = filterValue.split("TO");
                            if (rangeValues.length == 2) {
                                if ("min".equalsIgnoreCase(rangeValues[0].trim())) {
                                    if (rangeValues[1].trim().matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}"))
                                        boolQuery.addMust(queryBuilder.le("updateTime", rangeValues[1].trim()));
                                } else if ("max".equalsIgnoreCase(rangeValues[1].trim())) {
                                    if (rangeValues[0].trim().matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}"))
                                        boolQuery.addMust(queryBuilder.ge("updateTime", rangeValues[0].trim()));
                                } else {
                                    if (rangeValues[0].trim().matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}")
                                            && rangeValues[1].trim().matches(
                                            "\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}"))
                                        boolQuery.addMust(queryBuilder.between("updateTime", rangeValues[0].trim(),
                                                rangeValues[1].trim(), true));
                                }
                            }
                        }
                    } else if ("not".equalsIgnoreCase(filterType)) {
                        if (!"".equals(filterValue)) {
                            filterValue = filterValue.replaceAll("[ ]+", " OR ");
                            boolQuery.addMustNot(queryBuilder.queryString(filterField + ":(" + filterValue + ")")
                                    .toQuery());
                        }
                    }
                }
            } catch (JSONException e) {
            }
            CompassHits hits = null;
            // long beforeSearch = System.currentTimeMillis();
            try {
                String sortType = jo.getString("order");
                if ("time".equalsIgnoreCase(sortType))
                    hits = boolQuery.toQuery().addSort("updateTime", CompassQuery.SortDirection.REVERSE).hits();
            } catch (JSONException e) {
                hits = boolQuery.toQuery().hits();// .setAliases(new
                // String[]{"info","program","schedule","entrance","series"})
            }

            if (hits.length() == 0) {
                response.getWriter().write("{\"totalCount\":0,\"results\":[]}");
                return;
            }
            JSONArray array = new JSONArray();
            // long afterSearch = System.currentTimeMillis();
            try {
                int start = jo.getInt("start");
                int limit = jo.getInt("limit");
                if (limit <= 0 || start < 0) {
                    response.getWriter().write("search condition wrong! Limit should > 0 and start >= 0");
                    return;
                }
                try {
                    if (start + limit < hits.length()) {
                        for (int i = start; i < start + limit; ++i) {
                            Info info = (Info) hits.data(i);
                            array.put(infoToJsonObject(info));
                        }
                    } else if (start < hits.length()) {
                        for (int i = start; i < hits.length(); ++i) {
                            Info info = (Info) hits.data(i);
                            array.put(infoToJsonObject(info));
                        }
                    } else {
                        response.getWriter().write("[超出页数范围]");
                        return;
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                    response.getWriter().write("info TO JSONObject wrong!");
                    return;
                }
            } catch (JSONException e) {
                for (int i = 0; i < hits.length(); ++i) {
                    Info info = (Info) hits.data(i);
                    try {
                        array.put(infoToJsonObject(info));
                    } catch (JSONException el) {
                        el.printStackTrace();
                        response.getWriter().write("info TO JSONObject wrong!");
                        return;
                    }
                }
            }

            response.getWriter().write("{\"totalCount\":\"" + hits.length() + "\",\"results\":" + "}");
            return;
        } catch (JSONException e) {
            e.printStackTrace();
            response.getWriter().write("error: JSON Wrong");
            return;
        } finally {
            session.close();
        }
    }

    private JSONObject infoToJsonObject(final Info info) throws JSONException {
        JSONObject jo = new JSONObject();
        jo.put("id", info.getId());
        jo.put("name", info.getName());
        jo.put("subtype", info.getSubType() == null ? "" : info.getSubType().getName());
        jo.put("type", info.getType() == null ? "" : info.getType().getName());
        jo.put("p_type", info.getP_type());
        jo.put("updateTime",
                info.getUpdateTime() == null ? "" : DateUtil.convertDateToString("yyyy-MM-dd HH:mm",
                        info.getUpdateTime()));
        Set<Tag> tagSet = info.getTags();
        StringBuffer tagBuffer = new StringBuffer();
        for (Tag t : tagSet)
            tagBuffer.append(t.getTag()).append("  ");
        // jo.put("mediacode", info.getCode());
        jo.put("tags", tagBuffer.toString());
        jo.put("description", info.getDescription() == null ? "" : info.getDescription());
        Set<Keyword> keySet = info.getKeywords();
        StringBuffer keySb = new StringBuffer();
        for (Keyword k : keySet)
            keySb.append(k.getKeyword()).append("  ");
        jo.put("keywords", keySb.toString());
        jo.put("genre", info.getGenre());
        if (info.getManualBinds().size() > 0) {
            StringBuffer sb = new StringBuffer();
            for (ManualBind mb : info.getManualBinds()) {
                sb.append(mb.getName());
                sb.append(" ");
            }
            jo.put("manualBind", sb.toString());
        }
        if (info instanceof Entrance) {
            Entrance et = (Entrance) info;
            jo.put("url", et.getUrl());
        } else if (info instanceof Program) {
            Program p = (Program) info;
            jo.put("mediacode", p.getCode());
            jo.put("actors", p.getActors() == null ? "" : p.getActors());
            jo.put("directors", p.getDirectors() == null ? "" : p.getDirectors());
        } else if (info instanceof Series) {
            Series p = (Series) info;
            jo.put("mediacode", p.getCode());
            jo.put("actors", p.getActors() == null ? "" : p.getActors());
            jo.put("directors", p.getDirectors() == null ? "" : p.getDirectors());
        } else if (info instanceof Schedule) {
            Schedule p = (Schedule) info;
            jo.put("mediacode", p.getCode());
            jo.put("actors", p.getActors() == null ? "" : p.getActors());
            jo.put("directors", p.getDirectors() == null ? "" : p.getDirectors());
            jo.put("channel", p.getChannel());
            if (p.getEndPlayTime().before(new Date())) {
                jo.put("type", "回看");
                jo.put("startPlayTime", DateUtil.convertDateToString("yyyy-MM-dd HH:mm", p.getStartPlayTime()));
                jo.put("endPlayTime", DateUtil.convertDateToString("yyyy-MM-dd HH:mm", p.getEndPlayTime()));
            } else {
                jo.put("type", "预告");
                jo.put("startPlayTime", DateUtil.convertDateToString("yyyy-MM-dd HH:mm", p.getStartPlayTime()));
                jo.put("endPlayTime", DateUtil.convertDateToString("yyyy-MM-dd HH:mm", p.getEndPlayTime()));
            }
        }
        return jo;
    }
}
